<?
import('japha.lang.ThreadDeath');
import('japha.util.Vector');
import('japha.util.Enumeration');
import('com.japha.iterator.EnumerationIterator');

require_once "Protectable.php";

/**
 * A <code>TestResult</code> collects the results of executing
 * a test case. It is an instance of the Collecting Parameter pattern.
 * The test framework distinguishes between <i>failures</i> and <i>errors</i>.
 * A failure is anticipated and checked for with assertions. Errors are
 * unanticipated problems like an <code>ArrayIndexOutOfBoundsException</code>.
 *
 * @see Test
 * @author Ryan Gantt
 */
class TestResult extends Object
{
	/** 
	 * Vector
	 */
	protected $fFailures;
	
	/**
	 * Vector
	 */
	protected $fErrors;
	
	/**
	 * Vector
	 */
	protected $fListeners;
	
	/**
	 * int
	 */
	protected $fRunTests;
	
	/**
	 * boolean
	 */
	private $fStop;
	
	/**
	 * Initializes all of the instance members to new instances of their respective implementations
	 */
	public function __construct() 
	{
		$this->fFailures = new Vector();
		$this->fErrors = new Vector();
		$this->fListeners = new Vector();
		$this->fRunTests = 0;
		$this->fStop = false;
	}
	
	/**
	 * Adds an error to the list of errors. The passed in exception
	 * caused the error.
	 *
	 * @synchronized
	 */
	public function addError( Test $test, Throwable $t )
	{
		$this->fErrors->addElement( new TestFailure( $test, $t ) );
		for ( $e = $this->getListIterator(), $e->reset(); $e->hasNext(); $e->next() )
		{
			$e->current()->addError( $test, $t ); // instance of TestListener
		}
	}
	
	/**
	 * Adds a failure to the list of failures. The passed in exception
	 * caused the failure.
	 *
	 * @synchronized
	 */
	public function addFailure( Test $test, AssertionFailedError $t )
	{
		$this->fFailures->addElement( new TestFailure( $test, $t ) );
		for ( $e = $this->getListIterator(), $e->reset(); $e->hasNext(); $e->next() )
		{
			$e->current()->addFailure( $test, $t ); // instance of TestListener
		}
	}
	
	/**
	 * Registers a TestListener
	 *
	 * @synchronized
	 */
	public function addListener( TestListener $listener )
	{
		$this->fListeners->addElement( $listener );
	}
	
	/**
	 * Unregisters a TestListener
	 *
	 * @synchronized
	 */
	public function removeListener( TestListener $listener )
	{
		$this->fListeners->removeElement( $listener );
	}
	
	/**
	 * Returns a copy of the listeners.
	 *
	 * @synchronized
	 */
	private function cloneListeners()
	{
		return $this->fListeners->_clone();
	}
	
	/**
	 * Returns an iterator over the listeners
	 */
	private function getListIterator()
	{
		return new EnumerationIterator( $this->cloneListeners()->elements() );	
	}
	
	/**
	 * Informs the result that a test was completed.
	 */
	public function endTest( Test $test )
	{
		for ( $e = $this->getListIterator(), $e->reset(); $e->hasNext(); $e->next() )
		{
			$e->current()->endTest( $test ); // instanceof TestListener
		}
	}
	
	/**
	 * Gets the number of detected errors.
	 *
	 * @synchronized
	 */
	public function errorCount()
	{
		return $this->fErrors->size();
	}
	
	/**
	 * Returns an Enumeration for the errors
	 *
	 * @synchronized
	 */
	public function errors()
	{
		return $this->fErrors->elements();
	}
	
	/**
	 * Gets the number of detected failures.
	 *
	 * @synchronized
	 */
	public function failureCount()
	{
		return $this->fFailures->size();
	}
	
	/**
	 * Returns an Enumeration for the failures
	 *
	 * @synchronized
	 */
	public function failures()
	{
		return $this->fFailures->elements();
	}
	
	/**
	 * Runs a TestCase.
	 *
	 * This method should be protected
	 */
	public function run( TestCase $test ) 
	{
		$this->startTest( $test );
		$this->runProtected( $test, new ProtectableImpl() );
		$this->endTest( $test );
	}
	
	/**
	 * Gets the number of run tests.
	 *
	 * @synchronized
	 */
	public function runCount()
	{
		return $this->fRunTests;
	}
	
	/**
	 * Runs a TestCase.
	 */
	public function runProtected( Test $test, Protectable $p ) 
	{
		try 
		{
			$p->protect( $test );
		} 
		catch ( AssertionFailedError $e ) 
		{
			$this->addFailure( $test, $e );
		}
		catch ( ThreadDeath $e )
		{
			throw $e;
		}
		catch ( Throwable $e ) 
		{
			$this->addError( $test, $e );
		}
	}
	
	/**
	 * Checks whether the test run should stop
	 *
	 * @synchronized
	 */
	public function shouldStop()
	{
		return $this->fStop;
	}
	
	/**
	 * Informs the result that a test will be started.
	 */
	public function startTest( Test $test ) 
	{		
		$count = $test->countTestCases();
		$this->fRunTests += $count;
		$this->cloneListeners();
		for ( $e = $this->getListIterator(), $e->reset(); $e->hasNext(); $e->next() )
		{
			$e->current()->startTest( $test ); // instanceof TestListener
		}
	}
	
	/**
	 * Marks that the test run should stop.
	 *
	 * @synchronized
	 */
	public function stop()
	{
		$this->fStop = true;
	}
	
	/**
	 * Returns whether the entire test was successful or not.
	 *
	 * @synchronized
	 * @return boolean True iff there were no errors or failures
	 */
	public function wasSuccessful()
	{
		return ( ( $this->failureCount() == 0 ) && ( $this->errorCount() == 0 ) );
	}
}

/**
 * @author Ryan Gantt
 */
class ProtectableImpl extends TestResult implements Protectable
{
	/**
	 * Runs a method from the object of type Test in a protected environment (synchronized)
	 *
	 * @param test The object of type Test to run methods on
	 * @throws Throwable When an exception occurs within the protected environment
	 */
	public function protect( Test $test )
	{
		$test->runBare();
	}
}